package com.bsj.media.media;

import com.bsj.power.common.util.Conv;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class G711A {
	static final byte SIGN_BIT = (byte) 0x80; /* Sign bit for a A-law byte. */
	static final byte QUANT_MASK = 0xf; /* Quantization field mask. */
	static final byte NSEGS = 8; /* Number of A-law segments. */
	static final byte SEG_SHIFT = 4; /* Left shift for segment number. */
	static final byte SEG_MASK = 0x70; /* Segment field mask. */
	static final byte BIAS = (byte) (0x84); /* Bias for linear code. */

	static short seg_end[] = { 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF };

	/*
	 * alaw2linear() - Convert an A-law value to 16-bit linear PCM
	 *
	 */
	static int alaw2linear(byte a_val) {
		int t;
		int seg;

		a_val ^= 0x55;

		t = (a_val & QUANT_MASK) << 4;
		seg = (a_val & SEG_MASK) >> SEG_SHIFT;
		switch (seg) {
		case 0:
			t += 8;
			break;
		case 1:
			t += 0x108;
			break;
		default:
			t += 0x108;
			t <<= seg - 1;
		}

		return ((a_val & SIGN_BIT) != 0 ? t : -t);
	}

	/*
	 * ulaw2linear() - Convert a u-law value to 16-bit linear PCM
	 *
	 * First, a biased linear code is derived from the code word. An unbiased output
	 * can then be obtained by subtracting 33 from the biased code.
	 *
	 * Note that this function expects to be passed the complement of the original
	 * code word. This is in keeping with ISDN conventions.
	 */
	static int ulaw2linear(byte u_val) {
		int t;

		/* Complement to obtain normal u-law value. */
		u_val = (byte) ~u_val;

		/*
		 * Extract and bias the quantization bits. Then shift up by the segment number
		 * and subtract out the bias.
		 */
		t = ((u_val & QUANT_MASK) << 3) + BIAS;
		t <<= (u_val & SEG_MASK) >> SEG_SHIFT;

		return ((u_val & SIGN_BIT) != 0 ? (BIAS - t) : (t - BIAS));
	}

	static int search(int val, short[] table, int size) {
		int i;
		int j = 0;
		for (i = 0; i < size; i++) {
			if (val <= table[j++])
				return (i);
		}
		return (size);
	}

	// Convert a 16-bit linear PCM value to 8-bit A-law
	static byte linear2alaw(int pcm_val) {
		int mask;
		int seg;
		byte aval; // unsigned char

		int temp = 0; // 临时变量

		if (pcm_val >= 0) {
			mask = 0xD5; /* sign (7th) bit = 1 */
		} else {
			mask = 0x55; /* sign bit = 0 */
			pcm_val = -pcm_val - 8;
		}

		/* Convert the scaled magnitude to segment number. */
		seg = search(pcm_val, seg_end, 8);

		/* Combine the sign, segment, and quantization bits. */

		if (seg >= 8) { /* out of range, return maximum value. */
			// return (byte) (0x7F ^ mask);
			temp = 0x7F ^ mask;
			return (byte) (temp & 0x000000FF);
		} else {
			aval = (byte) ((seg << SEG_SHIFT) & 0x000000FF);

			if (seg < 2) {
				// aval |= (pcm_val >> 4) & QUANT_MASK;
				temp = aval | ((pcm_val >> 4) & QUANT_MASK);
				aval = (byte) (temp & 0x000000FF);
			} else {
				// aval |= (pcm_val >> (seg + 3)) & QUANT_MASK;
				temp = aval | ((pcm_val >> (seg + 3)) & QUANT_MASK);
				aval = (byte) (temp & 0x000000FF);
			}
			// return (byte) (aval ^ mask);
			temp = aval ^ mask;
			return (byte) (temp & 0x000000FF);
		}
	}

	// Convert a 16-bit linear PCM value to 8-bit u-law
	static byte linear2ulaw(int pcm_val) {
		int mask;
		int seg;
		byte aval;

		if (pcm_val >= 0) {
			mask = 0xD5; /* sign (7th) bit = 1 */
		} else {
			mask = 0x55; /* sign bit = 0 */
			pcm_val = (short) (-pcm_val - 8);
		}

		/* Convert the scaled magnitude to segment number. */
		seg = search(pcm_val, seg_end, 8);

		/* Combine the sign, segment, and quantization bits. */

		if (seg >= 8) /* out of range, return maximum value. */
			return (byte) (0x7F ^ mask);
		else {
			aval = (byte) (seg << SEG_SHIFT);
			if (seg < 2)
				aval |= (pcm_val >> 4) & QUANT_MASK;
			else
				aval |= (pcm_val >> (seg + 3)) & QUANT_MASK;
			return (byte) (aval ^ mask);
		}
	}

	/**
	 * 解码G711a数据
	 * 
	 * @param audioBuf
	 * @param length
	 * @param pcm
	 * @return
	 */
	public static int decoder(byte[] audioBuf, int inOffset, int length, byte[] pcm) {
		if (audioBuf == null || pcm.length < (length << 1)) {
			return 0;
		}

		int outIndex = 0;
		int len = length + inOffset;
		for (int i = inOffset; i < len; ++i) {
			short dec = (short) alaw2linear(audioBuf[i]);
			Conv.setShortHostOrder(pcm, outIndex, dec);
			outIndex += 2;
		}


		return outIndex;
	}

	/**
	 * pcm编码成G711a
	 * 
	 * @param pcmBuf
	 * @param length
	 * @param alaw
	 * @param alawOffset 输出buff的偏移位
	 * @return
	 */
	public static int encoder(byte[] pcmBuf, int length, byte[] alaw, int alawOffset) {
		if (pcmBuf == null || alaw.length < (length >> 1)) {
			return 0;
		}

		int outIndex = alawOffset;
		for (int i = 0; i < length; i += 2) {
			int pcm = pcmBuf[i] + (pcmBuf[i + 1] << 8);
			alaw[outIndex++] = linear2alaw(pcm);
		}

		return (outIndex - alawOffset);
	}
}
